package com.example.win.android_banduyujia.peopleactivity.ui.polites;

import android.content.res.Configuration;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import java.util.Vector;

/**
 * Created by Win on 2015/9/18.
 */
public class GestureImageViewTouchListener {

    private GestureImageView     image;
    private View.OnClickListener onClickListener;

    private final PointF current = new PointF();
    private final PointF last = new PointF();
    private final PointF next = new PointF();
    private final PointF midpoint = new PointF();

    private final VectorF scaleVector = new VectorF();
    private final VectorF pinchVector = new VectorF();

    private boolean touched = false;
    private boolean inZoom = false;

    private float initialDistance;
    private float lastScale = 1.0f;
    private float currentScale = 1.0f;

    private float boundaryLeft = 0;
    private float boundaryTop = 0;
    private float boundaryRight = 0;
    private float boundaryBottom = 0;

    private float minScale = 5.0f;
    private float maxScale = 0.25f;
    private float fitScaleHorizontal = 1.0f;
    private float fitScaleVertical = 1.0f;

    private int canvasWidth = 0;
    private int canvasHeight = 0;

    private float centerX = 0;
    private float centerY = 0;

    private float startingScale = 0;

    private boolean canDragX = false;
    private boolean canDragY = false;

    private boolean multiTouch = false;

    private int displayWidth;
    private int displayHeight;

    private int imageWidth;
    private int imageHeight;

    private FlingListener  flingListener;
    private FlingAnimation  flingAnimation;
    private ZoomAnimation   zoomAnimation;
    private MoveAnimation   moveAnimation;
    private GestureDetector tapDetector;
    private GestureDetector flingDetector;
    private GestureImageViewListener imageListener;

    public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) {
         super();

         this.image = image;

         this.displayWidth = displayWidth;
         this.displayHeight = displayHeight;

        this.centerX = (float)displayWidth / 2.0f;
        this.centerY = (float)displayHeight / 2.0f;

        this.imageWidth = image.getImageWidth();
        this.imageHeight = image.getImageHeight();

        startingScale = image.getScale();

        currentScale = startingScale;
        lastScale = startingScale;

        boundaryRight = displayWidth;
        boundaryBottom = displayHeight;
        boundaryLeft = 0;
        boundaryTop = 0;

        next.x = image.getImageX();
        next.y = image.getImageY();

        flingListener = new FlingListener();
        flingAnimation = new FlingAnimation();
        zoomAnimation = new ZoomAnimation();
        moveAnimation = new MoveAnimation();

        flingAnimation.setListener(new FlingAnimationListener() {
            public void onMove(float x, float y) {
                handleDrag(current.x + x, current.y + y);
            }

            public void onComplete() {}
        });
        zoomAnimation.setZoom(2.0f);
        zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
            @Override
            public void onZoom(float scale, float x, float y) {
                if (scale <= maxScale && scale >= minScale) {
                    handleScale(scale, x, y);
                }
            }

            @Override
            public void onComplete() {
                inZoom = false;
                handleUp();
            }
        });
        moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {

            @Override
            public void onMove(float x, float y) {
                image.setPosition(x, y);
                image.redraw();
            }
        });
        tapDetector = new GestureDetector(image.getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                startZoom(e);
                return true;
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                if(!inZoom) {
                    if(onClickListener != null) {
                        onClickListener.onClick(image);
                        return true;
                    }
                }
                return false;
            }
        });
        flingDetector = new GestureDetector(image.getContext(), flingListener);
        imageListener = image.getGestureImageViewListener();

        calculateBoundaries();
    }

    private void startFling() {
        flingAnimation.setVelocityX(flingListener.getVelocityX());
        flingAnimation.setVelocityY(flingListener.getVelocityY());
        image.animationStart((Animation) flingAnimation);
    }

    private void startZoom(MotionEvent e) {
       inZoom = true;
       zoomAnimation.reset();

       float zoomTo;

       if(image.isLandscape()){
           if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
               int scaledHeight = image.getScaledHeight();

               if(scaledHeight < canvasHeight) {
                   zoomTo = fitScaleVertical / currentScale;
                   zoomAnimation.setTouchX(e.getX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
               else {
                   zoomTo = fitScaleHorizontal / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
       }
           else {
               int scaledWidth = image.getScaledWidth();

               if(scaledWidth == canvasWidth) {
                   zoomTo = currentScale*4.0f;
                   zoomAnimation.setTouchX(e.getX());
                   zoomAnimation.setTouchY(e.getY());
               }
               else if(scaledWidth < canvasWidth) {
                   zoomTo = fitScaleHorizontal / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(e.getY());
               }
               else {
                   zoomTo = fitScaleHorizontal / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
           }
       }
       else {
           if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {

               int scaledHeight = image.getScaledHeight();

               if(scaledHeight == canvasHeight) {
                   zoomTo = currentScale*4.0f;
                   zoomAnimation.setTouchX(e.getX());
                   zoomAnimation.setTouchY(e.getY());
               }
               else if(scaledHeight < canvasHeight) {
                   zoomTo = fitScaleVertical / currentScale;
                   zoomAnimation.setTouchX(e.getX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
               else {
                   zoomTo = fitScaleVertical / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
           }
           else {
               int scaledWidth = image.getScaledWidth();

               if(scaledWidth < canvasWidth) {
                   zoomTo = fitScaleHorizontal / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(e.getY());
               }
               else {
                   zoomTo = fitScaleVertical / currentScale;
                   zoomAnimation.setTouchX(image.getCenterX());
                   zoomAnimation.setTouchY(image.getCenterY());
               }
           }
       }

        zoomAnimation.setZoom(zoomTo);
        image.animationStart(zoomAnimation);
    }

    private void stopAnimations() {
        image.animationStop();
    }

    public boolean onTouch(View v, MotionEvent event){
        if(!inZoom) {

            if(!tapDetector.onTouchEvent(event)) {
                if(event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
                    startFling();
                }

                if(event.getAction() == MotionEvent.ACTION_UP) {
                    handleUp();
                }
                else if(event.getAction() == MotionEvent.ACTION_DOWN) {
                    stopAnimations();

                    last.x = event.getX();
                    last.y = event.getY();

                    if(imageListener != null) {
                        imageListener.onTouch(last.x, last.y);
                    }

                    touched = true;
                }
                else if(event.getAction() == MotionEvent.ACTION_MOVE) {
                    if(event.getPointerCount() > 1) {
                        multiTouch = true;
                        if(initialDistance > 0) {

                            pinchVector.set(event);
                            pinchVector.calculateLength();

                            float distance = pinchVector.length;

                            if(initialDistance != distance) {

                                float newScale = (distance / initialDistance) * lastScale;

                                if(newScale <= maxScale) {
                                    scaleVector.length *= newScale;

                                    scaleVector.calculateEndPoint();

                                    scaleVector.length /= newScale;

                                    float newX = scaleVector.end.x;
                                    float newY = scaleVector.end.y;

                                    handleScale(newScale, newX, newY);
                                }
                            }
                        }
                        else {
                            initialDistance = MathUtils.distance(event);

                            MathUtils.midpoint(event, midpoint);

                            scaleVector.setStart(midpoint);
                            scaleVector.setEnd(next);

                            scaleVector.calculateLength();
                            scaleVector.calculateAngle();

                            scaleVector.length /= lastScale;
                        }
                    }
                    else {
                        if(!touched) {
                            touched = true;
                            last.x = event.getX();
                            last.y = event.getY();
                            next.x = image.getImageX();
                            next.y = image.getImageY();
                        }
                        else if(!multiTouch) {
                            if(handleDrag(event.getX(), event.getY())) {
                                image.redraw();
                            }
                        }
                    }
                }
            }
        }
        return true;
    }

    private void handleUp() {
        multiTouch = false;

        initialDistance = 0;
        lastScale = currentScale;

        if(!canDragX){
            next.x = centerX;
        }
        if(!canDragY) {
            next.y = centerY;
        }
        boundCoordinates();

        if(!canDragX && !canDragY) {

            if(image.isLandscape()) {
                currentScale = fitScaleHorizontal;
                lastScale = fitScaleHorizontal;
            }
            else {
                currentScale = fitScaleVertical;
                lastScale = fitScaleVertical;
            }
        }

        image.setScale(currentScale);
        image.setPosition(next.x, next.y);

        if(imageListener != null) {
            imageListener.onScale(currentScale);
            imageListener.onPosition(next.x, next.y);
        }

        image.redraw();
    }

    private void handleScale(float scale, float x, float y) {
        currentScale = scale;

        if(currentScale > maxScale) {
            currentScale = maxScale;
        }
        else if (currentScale < minScale) {
            currentScale = minScale;
        }
        else {
            next.x = x;
            next.y = y;
        }

        calculateBoundaries();

        image.setScale(currentScale);
        image.setPosition(next.x, next.y);

        if(imageListener != null) {
            imageListener.onScale(currentScale);
            imageListener.onPosition(next.x, next.y);
        }

        image.redraw();
    }

    private boolean handleDrag(float x, float y) {
        current.x = x;
        current.y = y;

        float diffX = (current.x - last.x);
        float diffY = (current.y - last.y);

        if(diffX != 0 || diffY != 0) {

            if(canDragX) next.x += diffX;
            if(canDragY) next.y += diffY;

            boundCoordinates();

            last.x = current.x;
            last.y = current.y;

            if(canDragX || canDragY) {
                image.setPosition(next.x, next.y);

                if(imageListener != null) {
                    imageListener.onPosition(next.x, next.y);
                }

                return true;
            }
        }
        return false;
    }

    public void reset() {
        currentScale = startingScale;
        next.x = centerX;
        next.y = centerY;
        calculateBoundaries();
        image.setScale(currentScale);
        image.setPosition(next.x, next.y);
        image.redraw();
    }
    public float getMaxScale() {
        return maxScale;
    }

    public void setMaxScale(float maxScale) {
        this.maxScale = maxScale;
    }

    public float getMinScale() {
        return minScale;
    }

    public void setMinScale(float minScale) {
        this.minScale = minScale;
    }

    public void setOnClickListener(View.OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    protected void setCanvasWidth(int canvasWidth) {
        this.canvasWidth = canvasWidth;
    }

    protected void setCanvasHeight(int canvasHeight) {
        this.canvasHeight = canvasHeight;
    }

    protected void setFitScaleHorizontal(float fitScale) {
        this.fitScaleHorizontal = fitScale;
    }

    protected void setFitScaleVertical(float fitScaleVertical) {
        this.fitScaleVertical = fitScaleVertical;
    }

    protected void boundCoordinates() {
        if(next.x < boundaryLeft) {
            next.x = boundaryLeft;
        }
        else if(next.x > boundaryRight) {
            next.x = boundaryRight;
        }

        if(next.y < boundaryTop) {
            next.y = boundaryTop;
        }
        else if(next.y > boundaryBottom) {
            next.y = boundaryBottom;
        }
    }

    protected void calculateBoundaries() {

        int effectiveWidth = Math.round( (float) imageWidth * currentScale );
        int effectiveHeight = Math.round( (float) imageHeight * currentScale );

        canDragX = effectiveWidth > displayWidth;
        canDragY = effectiveHeight > displayHeight;

        if(canDragX) {
            float diff = (float)(effectiveWidth - displayWidth) / 2.0f;
            boundaryLeft = centerX - diff;
            boundaryRight = centerX + diff;
        }

        if(canDragY) {
            float diff = (float)(effectiveHeight - displayHeight) / 2.0f;
            boundaryTop = centerY - diff;
            boundaryBottom = centerY + diff;
        }
    }
}
